﻿// PaginationDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "Pagination.h"
#include "PaginationDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

	// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CPaginationDlg 对话框



CPaginationDlg::CPaginationDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_PAGINATION_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CPaginationDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_BUTTON4, readButton);
	DDX_Control(pDX, IDC_BUTTON3, saveButton);
	DDX_Control(pDX, IDC_BUTTON2, addButton);
	DDX_Control(pDX, IDC_BUTTON1, openButton);
	DDX_Control(pDX, IDC_EDIT1, pageNumber);
	DDX_Control(pDX, IDC_EDIT2, content);
	DDX_Control(pDX, IDC_COMBO1, pages);
}

BEGIN_MESSAGE_MAP(CPaginationDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON4, &CPaginationDlg::OnBnClickedReadButton)
	ON_BN_CLICKED(IDC_BUTTON3, &CPaginationDlg::OnBnClickedSaveButton)
	ON_BN_CLICKED(IDC_BUTTON2, &CPaginationDlg::OnBnClickedAddButton)
	ON_BN_CLICKED(IDC_BUTTON1, &CPaginationDlg::OnBnClickedOpenButton)
	ON_CBN_SELCHANGE(IDC_COMBO1, &CPaginationDlg::OnCbnSelchangeCombo1)
	ON_EN_CHANGE(IDC_EDIT2, &CPaginationDlg::OnEnChangeContentEdit)
	ON_WM_CLOSE()
END_MESSAGE_MAP()


// CPaginationDlg 消息处理程序

BOOL CPaginationDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 将“关于...”菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时，框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	pages.AddString(TEXT("请选择页面"));
	pages.SetCurSel(0);
	showPageNumbers();

	return TRUE;  // 除非将焦点设置到控件，否则返回 TRUE
}

void CPaginationDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 如果向对话框添加最小化按钮，则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序，
//  这将由框架自动完成。

void CPaginationDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CPaginationDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


/*读取一页文件并展示在IDC_EDIT2*/
void CPaginationDlg::OnBnClickedReadButton()
{
	// 如果没选中任何页面，则直接返回
	if (nowPageIndex == 0) {
		return;
	}
	std::string tmp;
	tmp = this->str.substr(pageIndex[nowPageIndex], MAX_PAGE_SIZE); // 页面未进行修改，则读取原本内容
	for (auto iter = newContents.begin(); iter != newContents.end(); iter++) {
		if (iter->first == nowPageIndex) { // 页面曾经发生过修改，则读取修改后的内容
			tmp = iter->second;
			break;
		}
	}
	// 调度此页面
	usePage(nowPageIndex);
	// 展示
	std::string s = replace_all(tmp, "\n", "\r\n");
	CString cStr(s.c_str());
	content.SetWindowTextW(cStr);
}

/*保存修改内容，写入文件*/
void CPaginationDlg::OnBnClickedSaveButton()
{
	//保存所有修改过的页面
	for (auto iter = newContents.begin(); iter != newContents.end(); iter++) {
		this->str.replace(pageIndex[iter->first], MAX_PAGE_SIZE, iter->second);
	}
	this->str = replace_all(this->str, "\r\n", "\n");

	//写入文件
	saveData();
	newContents.clear();

	//重分页
	paging();

	// 重新调用当前页面
	if (nowPageIndex != 0) {
		usePage(nowPageIndex);
		pages.SetCurSel(nowPageIndex);
		std::string tmp;
		tmp = this->str.substr(pageIndex[nowPageIndex], MAX_PAGE_SIZE);
		std::string s = replace_all(tmp, "\n", "\r\n");
		CString cStr(s.c_str());
		content.SetWindowTextW(cStr);
	}

	// 输出提示信息
	std::string tipStr = "Saved successfully!";
	CString tips(tipStr.c_str());
	MessageBox(tips);
}

/*在当前选中的页面后面添加一个页面。*/
void CPaginationDlg::OnBnClickedAddButton()
{
	if (strcmp(filePath.c_str(), "") == 0) {
		return;
	}

	CString tmpStr;
	this->nowPageIndex++; // 新建页面的位置
	std::map<int, std::string> tmp;
	tmpStr.Format(TEXT("%d"), pageIndex.size() + 1);
	pages.AddString(tmpStr); // 在下拉框中增加一个选项

	// 由于页号变更，则视为后面所有的页面内容都改变，内容变为其前一页的内容
	for (int i = this->nowPageIndex; i < pageIndex.size(); i++) {
		insertData(i, this->str.substr(pageIndex[i], MAX_PAGE_SIZE));
	}

	// 为新页面留下空间
	for (auto iter = newContents.begin(); iter != newContents.end(); iter++) {
		// 后面的页面后移一位
		if (iter->first >= this->nowPageIndex) {
			tmp.insert(std::pair<int, std::string>(iter->first + 1, iter->second));
		}
		// 前面的页面保持不动
		else {
			tmp.insert(std::pair<int, std::string>(iter->first, iter->second));
		}
	}
	// 转存回类内变量
	this->newContents = tmp;

	usePage(this->nowPageIndex); // 载入当前页面到内存中
	std::string s = "";
	CString cStr(s.c_str());
	content.SetWindowTextW(cStr);
	pages.SetCurSel(this->nowPageIndex); // 更改下拉框选中信息
}

/*使用图形化界面选择一个文件，将其模拟分页。*/
void CPaginationDlg::OnBnClickedOpenButton()
{
	//打开文件选择对话框、获取路径
	CString gReadFilePathName;
	CFileDialog fileDlg(true, _T("txt"), _T("*.txt"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("txt Files (*.txt)|*.txt|All File (*.*)|*.*||"), NULL);
	if (fileDlg.DoModal() == IDOK) {    //弹出对话框  
		gReadFilePathName = fileDlg.GetPathName();//得到完整的文件名和目录名拓展名  
		this->filePath = CT2A(gReadFilePathName.GetBuffer(0)); // CString -> std::string
	}
	else {
		return;
	}
	//载入文件
	std::ifstream fin(this->filePath);
	std::string str;
	while (getline(fin, str)) {
		str += '\n';
		this->str += str;
	}
	fin.close();

	//自动分页
	paging();

	// 输出提示信息
	std::string tipStr = "Successfully opened file: \"";
	tipStr += this->filePath;
	tipStr += "\"";
	CString tips(tipStr.c_str());
	MessageBox(tips);
}

/*选择文本框，获取Index*/
void CPaginationDlg::OnCbnSelchangeCombo1()
{
	this->nowPageIndex = pages.GetCurSel();
}

/*自动检测文本是否变化，实时保存新内容到newContents中。*/
void CPaginationDlg::OnEnChangeContentEdit()
{
	content.GetWindowTextW(this->nowNewContent); // 获取内容
	std::string s = CT2A(this->nowNewContent.GetBuffer(0)); // CString -> std::string
	setNewData(nowPageIndex, s); // 暂存
}

/*替换string中的所有指定字符*/
std::string& CPaginationDlg::replace_all(std::string& src, const std::string& old_value, const std::string& new_value) {
	// 每次重新定位起始位置，防止上轮替换后的字符串形成新的old_value
	for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) {
		if ((pos = src.find(old_value, pos)) != std::string::npos) {
			src.replace(pos, old_value.length(), new_value);
		}
		else break;
	}
	return src;
}

/*显示当前内存中的页面情况*/
void CPaginationDlg::showPageNumbers()
{
	std::string tmp = "";
	int cnt = 0;
	// 展示调入内存中的页号
	for (auto i = nowPages.begin(); i < nowPages.end(); i++) {
		tmp += std::to_string(*i);
		tmp += "  ";
		cnt++;
	}
	// 不满则以-1填充
	for (int i = cnt; i < MAX_PAGE_NUMBER; i++) {
		tmp += "-1  ";
	}
	CString showStr(tmp.c_str());
	pageNumber.SetWindowTextW(showStr);
}

/*载入一个页面到内存中*/
void CPaginationDlg::usePage(int index)
{
	// 判断当前调用的页面是否之前已经调入过内存，如果调用过则置于队尾
	for (auto i = nowPages.begin(); i < nowPages.end(); i++) {
		if (*i == index) {
			nowPages.erase(i);
			nowPages.push_back(index);
			showPageNumbers();
			return;
		}
	}

	// 当前页面未被调入过内存：
	if (nowPages.size() >= MAX_PAGE_NUMBER) { // 内存已满
		int outIndex = nowPages.front();
		for (auto iter = newContents.begin(); iter != newContents.end(); iter++) {
			if (iter->first == outIndex) {
				UINT nRet = MessageBox(_T("还有未保存的数据，是否进行保存？"), _T("close"), MB_YESNO);
				// 点击保存则保存
				if (nRet == IDYES) {
					this->str.replace(pageIndex[iter->first], MAX_PAGE_SIZE, iter->second);// 将原本的页的内容替换为内存中保存的内容
					saveData(); // 自动保存
				}
				newContents.erase(iter); // 清除已经写入的修改
				break;
			}
		}
		// 出队
		nowPages.pop_front();
	}
	// 新页面调入内存，即入队
	nowPages.push_back(index);
	// 展示调度结果
	showPageNumbers();
}

/*分页，每1KB分一页，1KB是想在比较小的样例中尽可能多的分页*/
void CPaginationDlg::paging()
{
	// 分页之前把之前的数据清除掉，使得本次分页不会受之前结果的影响
	pages.ResetContent(); // 清楚下拉框内容，以保证Add从0开始
	nowPages.clear();// 将所有的页都调出内存
	showPageNumbers(); // 展示清理内存后的结果
	pages.AddString(TEXT("请选择页面"));// 占第0位，让后面的定位更加方便，同时具有提示效果
	pages.SetCurSel(0); // 显示第0位
	content.SetWindowTextW(TEXT(""));// 清空内容区域的内容

	// 进行分页
	CString pageNumStr;
	pageIndex.clear();
	int cnt = 1; // cnt为页号
	int index = 0; // index为页的起始地址
	while (index <= str.length()) {
		pageIndex.insert(std::pair<int, int>(cnt, index));
		pageNumStr.Format(TEXT("%d"), cnt);
		pages.AddString(pageNumStr); // 在下拉框中进行显示
		cnt++;
		index += MAX_PAGE_SIZE;
	}
}

void CPaginationDlg::insertData(int index, std::string s)
{
	if (newContents.find(index) == newContents.end()) {
		newContents.insert(std::pair<int, std::string>(index, s));
	}
}

/*将更改后的界面保存到map中*/
void CPaginationDlg::setNewData(int index, std::string s)
{
	// 如果没有，则插入数据；否则，对原来key对应的value进行更改
	if (newContents.find(index) == newContents.end()) {
		newContents.insert(std::pair<int, std::string>(index, s));
	}
	else {
		newContents[index] = s;
	}
}

/*将数据写回文件中*/
void CPaginationDlg::saveData()
{
	std::ofstream saveFile;
	saveFile.open(filePath);	// 打开文件
	saveFile << replace_all(this->str, "\r\n", "\n");// 写入数据
	//换成\n是因为在程序中显示需要用\r\n而在文件中，只以\n保存即可，\r\n会多一个空行
	saveFile.close();				// 关闭文件
}

void CPaginationDlg::OnClose()
{
	// 有数据未保存
	if (newContents.size() > 0) {
		// 提示
		UINT nRet = MessageBox(_T("还有未保存的数据，是否进行保存？"), _T("close"), MB_YESNO);
		// 点击保存则保存
		if (nRet == IDYES){
			for (auto iter = newContents.begin(); iter != newContents.end(); iter++) {
				this->str.replace(pageIndex[iter->first], MAX_PAGE_SIZE, iter->second);
			}
			this->str = replace_all(this->str, "\r\n", "\n");

			//写入文件
			saveData();
			newContents.clear();
		}
	}
	CDialogEx::OnClose();
}
